package com.dsp.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.dsp.soundscape.model.AlbumInfo;
import com.dsp.soundscape.model.SongInfo;
import com.dsp.soundscape.model.ZipInfo;
import com.dsp.soundscape.server.SoundscapeLoader;

public class ZipUtil {
	private static final Logger log = Logger.getLogger(ZipUtil.class.toString());

	private static final int TEN_KILOBYTES = 10240;
	private static SoundscapeLoader loader;

	private static final Map <String, ZipInfo> inProgress = new HashMap <String, ZipInfo>();
	private static final ExecutorService threadPool = Executors.newCachedThreadPool();

	public void setLoader(SoundscapeLoader loader) {
		ZipUtil.loader = loader;
		log.info("ZipUtil Initialized [Soundscape " + loader.getVersion().getFullVersion() + "]");
	}

	public static String createM3U(AlbumInfo album, String url) {
		if (loader != null) {	
			log.info("Requested M3U for: " + album.getAlbum());
	
			String m3uFile = album.getId() + ".M3U";
			String outFilename = loader.getTemp() + m3uFile;
			
			File outFile = new File(outFilename);
			if (! outFile.exists()) {
				try {
					FileWriter writer = new FileWriter(outFile);
					for (SongInfo song : album.getSongs()) {
						writer.write(url + song.getFulluri() + "\n");
					}
					writer.close();
					
				} catch (Exception ex) {
					log.log(Level.SEVERE, "M3U Failed", ex);
					return null;
				}
			}
			
			return loader.getZips() + "/" + m3uFile;
		}
		
		return null;
	}
	
	public static ZipInfo createZipFile(AlbumInfo album) {
		if (loader != null) {	
			log.info("Requested ZIP for: " + album.getAlbum());

			String zipFile = album.getId() + ".zip";
			String outFilename = loader.getTemp() + zipFile;

			ZipInfo info = new ZipInfo();
			info.setFiles(album.getSongs().size() + (album.isImageAvailable() ? 1 : 0));
			info.setFileName(zipFile);
			info.setUrl(loader.getZips() + "/" + zipFile);
			info.setPath(outFilename);

			if (! new File(info.getPath()).exists()) {
				threadPool.submit(new ZipCreator(info, album));
				inProgress.put(album.getId(), info);
				
			} else {
				if (inProgress.containsKey(album.getId())) {
					log.info ("Zip In Progress, Attaching Request");
					info = inProgress.get(album.getId());
					
				} else {
					log.info ("Zip Already Exists, Returning Filename");
					info.setCompleted(true);
				}
			}

			return info;
			
		} else {
			ZipInfo failed = new ZipInfo();
			failed.setProgress(-1);
			return failed;
		}
	}

	public static ZipInfo getProgress(String id) {
		ZipInfo info = inProgress.get(id);
		if (info.isCompleted() || info.getProgress() == -1) {
			inProgress.remove(id);
		}
		return info;
	}

	public static class ZipCreator implements Callable <ZipInfo> {
		private ZipInfo info;
		private AlbumInfo album;

		public ZipCreator(ZipInfo info, AlbumInfo album) {
			this.album = album;
			this.info = info;
		}

		@Override
		public ZipInfo call() throws Exception {
			return createZip();
		}

		private ZipInfo createZip() {
			
			try {
				File parent = new File(loader.getTemp());
				if (!parent.exists()) {
					parent.mkdirs();
				}

				// Create the ZIP file
				ZipOutputStream out = null;

				// Create a buffer for reading the files
				byte[] buf = new byte[TEN_KILOBYTES];

				try {
					out = new ZipOutputStream(new FileOutputStream(info.getPath()));
					log.info("Created ZIP: " + info.getPath());

					// Compress the files
					for (SongInfo song : album.getSongs()) {
						String filepath = loader.getPathroot() + 
						"/" + album.getFullpath() + "/" + song.getFilepath();
						zipFile (out, buf, filepath, song.getFilepath(), album.getPath());
						info.tickProgress();
					}

					if (album.isImageAvailable()) {
						String filepath = loader.getPathroot() + 
						"/" + album.getFullpath() + "/" + album.getImagepath();
						zipFile (out, buf, filepath, album.getImagepath(), album.getPath());
						info.tickProgress();
					}

					info.setCompleted(true);

				} catch (Exception ex) {
					log.log(Level.SEVERE, "Zipping Failed", ex);
					new File(info.getPath()).delete();
					info.setProgress(-1);

				} finally {
					if (out != null) {
						out.close();
					}

					buf = null;
					out = null;
				}

			} catch (Exception ex) {
				info.setProgress(-1);
			}

			return info;
		}

		private void zipFile(ZipOutputStream out, byte[] buf, String path, String fileName, String dir) throws IOException {
			log.fine("Zipping File: " + path);
			out.putNextEntry(new ZipEntry(dir + "/" + fileName));

			FileInputStream in = new FileInputStream(path);
			int len;
			while ((len = in.read(buf)) > 0) {
				out.write(buf, 0, len);
			}

			out.closeEntry();
			in.close();
		}
	}

}